; 模拟保护模式下，特权级从0切换到3（从高特权级切换到低特权级）
%include "pm.inc"

jmp LABEL_BEGIN

[SECTION .gdt]
; GDT描述符
; 每一项的格式: 
;   数据段/代码段描述符：       段基址， 段界限，   属性
;   门描述符：                段偏移， 段选择符，  属性,    参数个数
LABEL_GDT:                  Descriptor      0,              0,                      0                               ; 空描述符
LABEL_DESC_CODE32_DPL0:     Descriptor      0,              SegCode32CPl0Len - 1,   DA_C + DA_32 + DA_DPL0          ; 特权级0的代码段
LABEL_DESC_VIDEO_DPL0:      Descriptor      0B8000h,        0ffffh,                 DA_DRW + DA_DPL0                ; 特权级0的显存地址段（可读可写数据段）
LABEL_DESC_STACK_DPL0:      Descriptor      0,              TopOfStackCpl0,         DA_DRWA + DA_32 + DA_DPL0       ; 特权级0的堆栈段                   

LABEL_DESC_CODE32_DPL3:     Descriptor      0,              SegCode32CPl3Len - 1,   DA_C + DA_32 + DA_DPL3          ; 特权级3的代码段
LABEL_DESC_VIDEO_DPL3:      Descriptor      0B8000h,        0ffffh,                 DA_DRW + DA_DPL3                ; 特权级3的显存地址段（可读可写数据段）
LABEL_DESC_STACK_DPL3:      Descriptor      0,              TopOfStackCpl3,         DA_DRWA + DA_32 + DA_DPL3       ; 特权级3的堆栈段

;LABEL_DESC_TSS:


; GDT结束

GdtLen equ $ - LABEL_GDT    ; GDT长度
GdtPtr dw GdtLen - 1        ; GDT界限
       dd 0                 ; GDT基地址


; GDT段选择符
SelectorCode32Cpl0          equ     LABEL_DESC_CODE32_DPL0 - LABEL_GDT
SelectorVideoCpl0           equ     LABEL_DESC_VIDEO_DPL0 - LABEL_GDT
SelectorStackCpl0           equ     LABEL_DESC_STACK_DPL0 - LABEL_GDT
SelectorCode32Cpl3          equ     LABEL_DESC_CODE32_DPL3 - LABEL_GDT + SA_DPL3
SelectorVideoCpl3           equ     LABEL_DESC_VIDEO_DPL3 - LABEL_GDT + SA_DPL3
SelectorStackCpl3           equ     LABEL_DESC_STACK_DPL3 - LABEL_GDT + SA_DPL3

; End of GDT SECTION


[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0100h ; 初始化各个段，sp设置为该值有啥讲究？

    ; 初始化32位代码段描述符
    xor eax, eax
    mov ax, cs
    shl eax, 4
    add eax, LABEL_SEG_CODE32_CPL0               ; 实模式下，线性地址=段基址*16+偏移
    mov word [LABEL_DESC_CODE32_DPL0 + 2], ax    ; 将eax寄存器（也就是线性地址）的低16位写入到代码段描述符的第2 3字节（以第0字节开始）
    shr eax, 16                             ; 获取线性地址的高16位，低16位在上面的步骤已经使用
    mov byte [LABEL_DESC_CODE32_DPL0 + 4], al    ; 将线性地址的高16位中的低8位写入到段描述符的第4字节
    mov byte [LABEL_DESC_CODE32_DPL0 + 7], ah    ; 将线性地址的高16位中的高8位写入到段描述符的第7字节

    xor eax, eax
    mov ax, cs
    shl eax, 4
    add eax, LABEL_SEG_CODE32_CPL3               ; 实模式下，线性地址=段基址*16+偏移
    mov word [LABEL_DESC_CODE32_DPL3 + 2], ax    ; 将eax寄存器（也就是线性地址）的低16位写入到代码段描述符的第2 3字节（以第0字节开始）
    shr eax, 16                             ; 获取线性地址的高16位，低16位在上面的步骤已经使用
    mov byte [LABEL_DESC_CODE32_DPL3 + 4], al    ; 将线性地址的高16位中的低8位写入到段描述符的第4字节
    mov byte [LABEL_DESC_CODE32_DPL3 + 7], ah    ; 将线性地址的高16位中的高8位写入到段描述符的第7字节


    ; 初始化32位堆栈段描述符
    xor eax, eax
    mov ax, cs
    shl eax, 4
    add eax, LABEL_STACK_CPL0
    mov word [LABEL_DESC_STACK_DPL0 + 2], ax
    shr eax, 16
    mov byte [LABEL_DESC_STACK_DPL0 + 4], al
    mov byte [LABEL_DESC_STACK_DPL0 + 7], ah

    xor eax, eax
    mov ax, cs
    shl eax, 4
    add eax, LABEL_STACK_CPL3
    mov word [LABEL_DESC_STACK_DPL3 + 2], ax
    shr eax, 16
    mov byte [LABEL_DESC_STACK_DPL3 + 4], al
    mov byte [LABEL_DESC_STACK_DPL3 + 7], ah

    ; 为加载GDTR做准备
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_GDT  ; gdt基地址 ---> eax
    mov dword [GdtPtr + 2], eax ; gdt基地址 ---> [GdtPtr + 2]

    ; 加载GDTR
    lgdt [GdtPtr]

    ; 关中断 保护模式下的中断处理机制和实模式下不一样，先简单关中断处理
    cli

    ; 打开地址线A20，历史原因，打开它才能访问超过1M的地址空间
    in al, 92h
    or al, 00000010b
    out 92h, al

    ; 准备切换到保护模式
    mov eax, cr0
    or eax, 1       ; CR0的最低位是PE，该值为1时表示启动分段保护模式，开启PE=1模式后，必须立马使用跳转命令，例如下面的jmp命令
    mov cr0, eax

    jmp dword SelectorCode32Cpl0:0

; End of [SECTION .s16]

[SECTION .code32]
[BITS 32]
LABEL_SEG_CODE32_CPL0:
    mov ax, SelectorVideoCpl0
    mov gs, ax  ; 视频段选择子

    mov edi, (80 * 11 + 0) * 2     ; 屏幕第11行，第79列
    mov ah, 0ch     ; 0000 黑底， 000C 红字
    mov al, '0'
    mov [gs:edi], ax

    ; 使用ret返回切换到特权级3
    xor eax, eax
    mov ax, SelectorStackCpl0
    mov ss, ax
    mov esp, TopOfStackCpl0

    push SelectorStackCpl3        ; 特权级3的堆栈ss
    push TopOfStackCpl3           ; 特权级3的esp
    ; 0个参数，此处无需要处理
    push SelectorCode32Cpl3       ; 特权级3的代码段选择符
    push 0                            ; 特权级代码段的偏移地址，实际就是入口地址，因为段选择符的基址已经是从入口地址处开始，所以偏移从0开始
    retf

SegCode32CPl0Len equ $ - LABEL_SEG_CODE32_CPL0
; End of SECTION .code32 cpl0

; 特权级0的堆栈段
[SECTION .data0]
ALIGN 32
[BITS 32]
LABEL_STACK_CPL0:
    times 512 db 0
TopOfStackCpl0 equ $ - LABEL_STACK_CPL0 - 1
; End of SECTION .data0


[SECTION .code32]
[BITS 32]
LABEL_SEG_CODE32_CPL3:
    mov ax, SelectorVideoCpl3
    mov gs, ax  ; 视频段选择子

    mov edi, (80 * 12 + 0) * 2     ; 屏幕第11行，第79列
    mov ah, 0ch     ; 0000 黑底， 000C 红字
    mov al, '3'
    mov [gs:edi], ax

    jmp $

SegCode32CPl3Len equ $ - LABEL_SEG_CODE32_CPL3
; End of SECTION .code32 cpl3

; 特权级3的堆栈段
[SECTION .data3]
ALIGN 32
[BITS 32]
LABEL_STACK_CPL3:
    times 512 db 0
TopOfStackCpl3 equ $ - LABEL_STACK_CPL3 - 1
; End of SECTION .data3